package cloud.health.framework.nat.proxy.domain;

import cloud.health.framework.computing.core.AbstractNettyClient;
import com.alibaba.fastjson.JSON;
import com.google.common.primitives.Bytes;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;

@Slf4j
public class Command extends AbstractNettyClient {

    private ChannelHandlerContext context;
    private String host;
    private Integer port;
    private static final CopyOnWriteArrayList<Map<String, Object>> applications = new CopyOnWriteArrayList<>();

    private Map<String, Object> map;
    private ChannelHandlerContext proxy;

    public static Command build(String host, Integer port, List<Map<String, Object>> applications) {
        Command it = new Command();
        it.host = host;
        it.port = port;
        Command.applications.clear();
        Command.applications.addAll(applications);
        return it;
    }

    public static Command build(String host, Integer port,Map<String, Object> map) {
        Command it = new Command();
        it.host = host;
        it.port = port;
        it.map = map;
        return it;
    }

    @Override
    protected String hostname() {
        return this.host;
    }

    @Override
    protected int port() {
        return this.port;
    }

    @Override
    protected ChannelInitializer<SocketChannel> channelInitializer() {
        return new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                log.info("ch = {}", ch);
                ch.pipeline().addLast(new CommandChannelHandler());
            }

        };
    }

    public ChannelHandlerContext getContext() {
        return context;
    }

    class CommandChannelHandler extends ChannelInboundHandlerAdapter {

        private CopyOnWriteArrayList<Byte> bytes = new CopyOnWriteArrayList<>();

        @SneakyThrows
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            context = ctx;
            log.info("channel is active, ctx = {}", ctx);
            if (Objects.isNull(map) && Objects.nonNull(applications)) {
                String str = "<!--" + JSON.toJSONString(applications) + "-->";
                ctx.writeAndFlush(Unpooled.wrappedBuffer(str.getBytes()));
            }
            if (Objects.nonNull(map)) {
                String remote_port = map.getOrDefault("port", "").toString();
                Optional<Map<String, Object>> opt = applications.stream()
                        .filter(it -> remote_port.equals(it.getOrDefault("remote_port", "").toString()))
                        // .map(it -> it.get("local_port").toString())
                        .findFirst();
                if (!opt.isPresent()) {
                    return;
                }
                String str = "<!--" + JSON.toJSONString(map) + "-->";
                ctx.writeAndFlush(Unpooled.wrappedBuffer(str.getBytes()));

                Client client = Client.build(ctx);
                client.run(opt.get().getOrDefault("local_ip", "127.0.0.1").toString(),
                        Integer.valueOf(opt.get().get("local_port").toString())
                );
                while(Objects.isNull(proxy)) {
                    proxy = client.getProxy();
                    Thread.sleep(10L);
                }
            }
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            log.info("channel is inactive, ctx = {}", ctx);
            ctx.close();
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            ByteBuf buf = (ByteBuf) msg;
            try {
                String str = buf.toString(StandardCharsets.UTF_8);
                log.info("ctx = {} str = {}", ctx, str);
                if (Objects.isNull(map)) {
                    Map<String, Object> map = JSON.parseObject(str, Map.class);
                    Command p = Command.build(host, port, map);
                    p.run();
                    return;
                }
                if (Objects.isNull(proxy)) {
                    byte[] b = new byte[buf.readableBytes()];
                    buf.readBytes(b);
                    bytes.addAll(Bytes.asList(b));
                }
                if (!bytes.isEmpty()) {
                    proxy.writeAndFlush(Unpooled.wrappedBuffer(Bytes.toArray(bytes)));
                    bytes.clear();
                }
                proxy.writeAndFlush(buf.copy());

            } finally {
                buf.release();
            }
        }

    }

}
